package cn.com.idmy.base.util;


import cn.com.idmy.base.exception.BizException;
import lombok.NoArgsConstructor;
import org.dromara.hutool.core.text.StrUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class SqlUtil {
    /**
     * 危险的 SQL 关键字集合
     */
    private static final Set<String> DANGEROUS_KEYWORDS = new HashSet<>(Arrays.asList(
            "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", "TRUNCATE",
            "EXEC", "EXECUTE", "GRANT", "REVOKE", "CALL", "MERGE", "REPLACE",
            "SET", "DECLARE", "BEGIN", "COMMIT", "ROLLBACK", "SAVEPOINT",
            "LOCK", "UNLOCK", "RENAME", "COMMENT"
    ));

    /**
     * SQL 注入特征模式
     */
    private static final Pattern SQL_INJECTION_PATTERN = Pattern.compile(
            "(?i)(" +
                    // SQL 注释
                    "--[^\\r\\n]*|" +
                    "/\\*[\\s\\S]*?\\*/|" +
                    // 特殊字符组合
                    ";\\s*(?:SELECT|INSERT|UPDATE|DELETE|DROP|CREATE)|" +
                    // Union 注入
                    "\\bUNION\\s+(?:ALL\\s+)?SELECT\\b|" +
                    // 系统表查询
                    "\\b(?:INFORMATION_SCHEMA|MYSQL|PERFORMANCE_SCHEMA)\\b|" +
                    // 危险函数
                    "\\b(?:BENCHMARK|SLEEP|LOAD_FILE|OUTFILE|DUMPFILE)\\b|" +
                    // 存储过程
                    "\\b(?:EXEC|EXECUTE|CALL)\\s*\\(|" +
                    // 批量执行
                    ";\\s*[A-Z]" +
                    ")",
            Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
    );

    /**
     * 验证 SQL 是否安全（只允许 SELECT 语句）
     *
     * @param sql 待验证的 SQL 语句
     * @return 清理后的 SQL 语句
     * @throws BizException 如果 SQL 不安全
     */
    @NotNull
    public static String checkSelect(@Nullable String sql) {
        if (StrUtil.isBlank(sql)) {
            throw new BizException("SQL 语句不能为空");
        }

        // 去除首尾空白字符
        String trimmedSql = sql.trim();

        // 检查是否以 SELECT 开头（忽略大小写）
        if (!trimmedSql.toUpperCase().startsWith("SELECT")) {
            throw new BizException("只允许执行 SELECT 语句");
        }

        // 检查危险关键字
        String upperSql = trimmedSql.toUpperCase();
        for (String keyword : DANGEROUS_KEYWORDS) {
            if (containsKeyword(upperSql, keyword)) {
                throw new BizException("SQL 语句包含危险关键字: " + keyword);
            }
        }

        // 检查 SQL 注入模式
        if (SQL_INJECTION_PATTERN.matcher(trimmedSql).find()) {
            throw new BizException("SQL 语句包含潜在的注入风险");
        }

        // 检查分号（防止多语句执行）
        if (containsMultipleStatements(trimmedSql)) {
            throw new BizException("不允许执行多条 SQL 语句");
        }
        return trimmedSql;
    }

    /**
     * 列名验证正则表达式（预编译）
     */
    private static final Pattern COLUMN_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)?$");

    /**
     * 表名验证正则表达式（预编译）
     */
    private static final Pattern TABLE_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+$");

    /**
     * 单引号字符串匹配正则（预编译）
     */
    private static final Pattern SINGLE_QUOTE_STRING_PATTERN = Pattern.compile("'[^']*'");

    /**
     * 双引号字符串匹配正则（预编译）
     */
    private static final Pattern DOUBLE_QUOTE_STRING_PATTERN = Pattern.compile("\"[^\"]*\"");

    /**
     * 单行注释匹配正则（预编译）
     */
    private static final Pattern SINGLE_LINE_COMMENT_PATTERN = Pattern.compile("--[^\\r\\n]*");

    /**
     * 多行注释匹配正则（预编译）
     */
    private static final Pattern MULTI_LINE_COMMENT_PATTERN = Pattern.compile("/\\*[\\s\\S]*?\\*/");

    /**
     * 验证列名是否安全
     *
     * @param columnName 列名
     * @throws BizException 如果列名不安全
     */
    public static String checkColumn(@NotNull String columnName) {
        if (StrUtil.isBlank(columnName)) {
            throw new BizException("列名不能为空");
        }

        // 只允许字母、数字、下划线和点号（表.列名）
        if (!COLUMN_NAME_PATTERN.matcher(columnName).matches()) {
            throw new BizException("列名包含非法字符: " + columnName);
        }

        // 检查长度
        if (columnName.length() > 64) {
            throw new BizException("列名长度超过限制: " + columnName);
        }
        return columnName;
    }

    /**
     * 验证表名是否安全
     *
     * @param tableName 表名
     * @throws BizException 如果表名不安全
     */
    public static void checkTable(@Nullable String tableName) {
        if (StrUtil.isBlank(tableName)) {
            throw new BizException("表名不能为空");
        }

        // 只允许字母、数字、下划线
        if (!TABLE_NAME_PATTERN.matcher(tableName).matches()) {
            throw new BizException("表名包含非法字符: " + tableName);
        }

        // 检查长度
        if (tableName.length() > 64) {
            throw new BizException("表名长度超过限制: " + tableName);
        }

        // 检查是否为系统表
        var upperTableName = tableName.toUpperCase();
        if (upperTableName.startsWith("INFORMATION_SCHEMA") ||
                upperTableName.startsWith("MYSQL") ||
                upperTableName.startsWith("PERFORMANCE_SCHEMA")) {
            throw new BizException("不允许访问系统表: " + tableName);
        }
    }

    /**
     * 关键字匹配模式缓存
     */
    private static final Map<String, Pattern> KEYWORD_PATTERN_CACHE = new ConcurrentHashMap<>();

    /**
     * 检查是否包含关键字（完整单词匹配）
     *
     * @param sql     SQL 语句（大写）
     * @param keyword 关键字（大写）
     * @return 是否包含
     */
    private static boolean containsKeyword(@NotNull String sql, @NotNull String keyword) {
        // 使用缓存的正则表达式，避免重复编译
        var pattern = KEYWORD_PATTERN_CACHE.computeIfAbsent(keyword, k -> Pattern.compile("\\b" + k + "\\b"));
        return pattern.matcher(sql).find();
    }

    /**
     * 检查是否包含多条语句
     *
     * @param sql SQL 语句
     * @return 是否包含多条语句
     */
    private static boolean containsMultipleStatements(@NotNull String sql) {
        // 移除字符串内容和注释
        var cleanedSql = removeStringAndComments(sql);
        // 检查是否有分号（排除最后的分号）
        cleanedSql = cleanedSql.trim();
        if (cleanedSql.endsWith(";")) {
            cleanedSql = cleanedSql.substring(0, cleanedSql.length() - 1);
        }
        return cleanedSql.contains(";");
    }

    /**
     * 移除 SQL 中的字符串内容和注释
     *
     * @param sql SQL 语句
     * @return 清理后的 SQL
     */
    @NotNull
    private static String removeStringAndComments(@NotNull String sql) {
        // 移除单引号字符串
        sql = SINGLE_QUOTE_STRING_PATTERN.matcher(sql).replaceAll("''");
        // 移除双引号字符串
        sql = DOUBLE_QUOTE_STRING_PATTERN.matcher(sql).replaceAll("\"\"");
        // 移除单行注释
        sql = SINGLE_LINE_COMMENT_PATTERN.matcher(sql).replaceAll("");
        // 移除多行注释
        sql = MULTI_LINE_COMMENT_PATTERN.matcher(sql).replaceAll("");
        return sql;
    }

    public static boolean toBoolean(int val) {
        return val > 0 || val == -2;
    }

    public static boolean toBoolean(long val) {
        return val > 0;
    }

    public static boolean toBoolean(int[] vals) {
        for (int val : vals) {
            if (toBoolean(val)) {
                return true;
            }
        }
        return false;
    }
}